package br.com.vti.leilao.model.dao;

import br.com.vti.leilao.model.annotation.OrderBy;
import br.com.vti.leilao.model.entity.Model;
import br.com.vti.leilao.model.entity.ModelCombo;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;

/**
 * Data Access Object para manipulação e persistencia do objeto de um dominio.
 * 
 * @author Ednardo Rubens
 * 
 * @param <E> entidade extends Model
 * 
 */
@Repository("dao")
@SuppressWarnings("unchecked")
public class DaoImpl<E extends Model<? extends Number>> implements Dao<E> {

    public transient static final Logger logger = Logger.getLogger(DaoImpl.class);
    
    private static final long serialVersionUID = -2636784609791186233L;
    
    @PersistenceContext(unitName = "leilaoPU")
    private transient EntityManager entityManager;

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#getEntityManager()
     */
    @Override
    public EntityManager getEntityManager() {
        return entityManager;
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#setEntityManager(javax.persistence.EntityManager)
     */
    @Override
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#inserir(br.com.vti.core.model.entity.Model)
     */
    @Override
    public void inserir(E entidade) {
        getEntityManager().persist(entidade);
        getEntityManager().flush();
        logger.debug("Item inserido com sucesso.");
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#atualizar(br.com.vti.core.model.entity.Model)
     */
    @Override
    public void atualizar(E entidade) {
        E merge = getEntityManager().merge(entidade);
        BeanUtils.copyProperties(merge, entidade);
        getEntityManager().flush();
        logger.debug("Item atualizado com sucesso.");
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#remover(br.com.vti.core.model.entity.Model)
     */
    @Override
    public void remover(E entidade) {
        entidade = (E) getEntityManager().getReference(entidade.getClass(), entidade.getId());
        getEntityManager().remove(entidade);
        getEntityManager().flush();
        logger.debug("Item removido com sucesso.");
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#procurarPorId(br.com.vti.core.model.entity.Model)
     */
    @Override
    public E procurarPorId(E entidade) {
        E entidadeAux = null;
        if (entidade.getId() != null) {
            entidadeAux = (E) getEntityManager().find(entidade.getClass(), entidade.getId());
        }
        return entidadeAux;
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#listarPorHql(java.lang.String, java.util.Map, int)
     */
    @Override
    public List<E> listarPorHql(String hql, Map<String, Object> parametros, int maxResults) {
        Query query = getEntityManager().createQuery(hql);
        if (parametros != null) {
            for (String key : parametros.keySet()) {
                Object object = parametros.get(key);
                query.setParameter(key, object);
            }
        }
        if (maxResults > 0) {
            query.setMaxResults(maxResults);
        }
        return query.getResultList();
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#consultarPorHql(java.lang.String, java.util.Map, int)
     */
    @Override
    public List<Object[]> consultarPorHql(String hql, Map<String, Object> parametros, int maxResults) {
        Query query = getEntityManager().createQuery(hql);
        if (parametros != null) {
            for (String key : parametros.keySet()) {
                Object object = parametros.get(key);
                query.setParameter(key, object);
            }
        }
        if (maxResults > 0) {
            query.setMaxResults(maxResults);
        }
        return query.getResultList();
    }

    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#listarPorExemplo(br.com.vti.core.model.entity.Model, int)
     */
    @Override
    public List<E> listarPorExemplo(E entidade, int maxResults) {
        Example example = new Example(entidade).ignoreCase().excludeZeroes();
        String hql = example.toHqlString();
        Class<? extends Model<? extends Number>> classe = (Class<? extends Model<? extends Number>>) entidade.getClass();
        if (classe.isAnnotationPresent(OrderBy.class)) {
            OrderBy orderBy = classe.getAnnotation(OrderBy.class);
            String value = orderBy.value();
            if (value != null && !value.isEmpty()) {
                hql += " ORDER BY " + value;
            }
        } else {
            hql += " ORDER BY id";
        }
        Query query = getEntityManager().createQuery(hql);
        Map<String, Object> parametros = example.getPropertyValues();
        if (parametros != null) {
            for (String key : parametros.keySet()) {
                Object object = parametros.get(key);
                query.setParameter(key, object);
            }
        }
        if (maxResults > 0) {
            query.setMaxResults(maxResults);
        }
        return query.getResultList();
    }
    
    /* (non-Javadoc)
     * @see br.com.vti.leilao.model.dao.Dao#listarParaCombo(java.lang.Class)
     */
    @Override
    public List<E> listarParaCombo(Class<E> classe) {
        List<E> lista = new ArrayList<E>();
        try {
            Constructor<E> constructor = classe.getConstructor(Integer.class, String.class);
            if (constructor != null && ModelCombo.class.isAssignableFrom(classe)) {
                String hql = "SELECT NEW " + classe.getSimpleName() + "(id, descricao) FROM "
                        + classe.getSimpleName() + " ORDER BY descricao";
                Query query = getEntityManager().createQuery(hql);
                lista = query.getResultList();
            } else {
                logger.error("A entidade " + classe.getSimpleName() + " não implementa a interface ModelCombo");
            }
        } catch (NoSuchMethodException ex) {
            logger.error("A entidade " + classe.getSimpleName() + " não possui um construtor(id, descricao)", ex);
        }
        return lista;
    }

}
